本章概要
- 学习材料:中性净值、收益率与行业分组教学示例。
- 本章任务:运行
lst-drawdown、lst-rolling-stats 与 lst-correlation,计算回撤、滚动统计和关系矩阵。
- 完成后你将得到:最大回撤表、滚动窗口尾值、最大带符号相关对与绝对值最强相关对。
- 自我检查:手算峰谷回撤,排除对角/重复对,并分别核对
idxmax() 与 abs().idxmax();窗口不足或两种口径混写时先检查原因。
- 拓展练习:把同一组统计方法应用到本地真实净值,并说明数据来源。
本章学习目标
- 掌握 Pandas 描述性统计方法(均值、中位数、标准差等)
- 理解偏度与峰度的金融含义
- 学会使用 累积统计量 进行回撤分析
- 掌握 滚动窗口(rolling)与扩展窗口(expanding)计算
- 运用 groupby 进行分组统计
- 理解协方差与相关系数在投资组合中的作用
统计计算是数据分析的核心
统计学是从数据中提取知识的科学。在金融领域,统计计算帮助我们:
- 量化风险:计算波动率、VaR(在险价值)
- 评估收益:计算收益率、夏普比率
- 发现规律:识别相关性、趋势、周期性
- 做出决策:基于统计显著性的投资决策
Pandas 提供了丰富的统计计算方法,结合 NumPy 的向量化运算,可以高效处理大规模金融数据。
描述性统计:集中趋势
给定数据集 \(X = \{x_1, x_2, \ldots, x_n\}\):
| 均值 (Mean) |
数据的”重心” |
\(\bar{x} = \frac{1}{n}\sum_{i=1}^n x_i\) |
| 中位数 (Median) |
排序后中间位置的值 |
将数据分为两半 |
| 众数 (Mode) |
出现频率最高的值 |
最常见的观测值 |
描述性统计:离散程度与分布形状
| 样本方差 (Variance) |
偏离均值的平方距离 |
\(s^2 = \frac{1}{n-1}\sum_{i=1}^n (x_i - \bar{x})^2\) |
| 样本标准差 (Std Dev) |
样本方差的平方根 |
\(s = \sqrt{s^2}\) |
| 极差 (Range) |
最大值与最小值之差 |
\(\max(X) - \min(X)\) |
分布形状:
- 偏度 (Skewness):衡量分布的对称性
- 峰度 (Kurtosis):衡量分布的尖峰/厚尾程度
Pandas 口径:Series.var() 与 Series.std() 默认 ddof=1;总体统计需显式设置 ddof=0。
运行前预测|平台任务解答代码
- 输入预测:运行前先写出
value_QDII、value_QDII_diff、value_QDII_pctchangel、value_QDII_pctchange2 的业务含义、数据类型或取值范围,并判断哪一个输入最可能改变结果。
- 结果预测:不展开答案,先预测将得到
value_QDII_pctchange2 的结果;同时写出方向、数量级或表格/图形结构。
- 完成要求:能独立说明本任务从输入到“平台任务解答代码”结果的关键步骤,原样录入平台代码并得到可核对的运行结果。
⭐ 平台任务解答代码
展开完整代码(投影默认折叠)
# ⚠️ 平台原始代码 - 请原样输入至教学平台(注释除外),平台才会判定答案正确
#任务一
import pandas as pd
import matplotlib.pyplot as plt # 导入Matplotlib绘图库
# 从Excel文件读取数据存入value_QDII
value_QDII = pd.read_excel("https://huoran.oss-cn-shenzhen.aliyuncs.com/1726648479386.xlsx")
value_QDII["日期"] = pd.to_datetime(value_QDII["日期"] , format='%Y%m%d') # 转换为日期时间格式
value_QDII.set_index("日期",inplace=True) # 将日期列设为value_QDII数据框的索引
value_QDII = value_QDII.dropna() #删除缺失值所在行
(value_QDII/value_QDII.iloc[0]).plot(figsize=(8,6),grid=True) #将基金净值按首个交易日进行归一处理并可视化
plt.savefig("1.png") # 保存图形至文件
#任务二
import pandas as pd
# 从Excel文件读取数据存入value_QDII
value_QDII = pd.read_excel("https://huoran.oss-cn-shenzhen.aliyuncs.com/1726648479386.xlsx")
value_QDII["日期"] = pd.to_datetime(value_QDII["日期"] , format='%Y%m%d') # 转换为日期时间格式
value_QDII.set_index("日期",inplace=True) # 将日期列设为value_QDII数据框的索引
print(value_QDII.max()) #找出每只基金净值的最大值
print(value_QDII.min()) #找出每只基金净值的最小值
print(value_QDII.idxmax()) #最大值所在的索引值
print(value_QDII.idxmin()) #最小值所在的索引值
#任务三
import pandas as pd
# 从Excel文件读取数据存入value_QDII
value_QDII = pd.read_excel("https://huoran.oss-cn-shenzhen.aliyuncs.com/1726648479386.xlsx")
value_QDII["日期"] = pd.to_datetime(value_QDII["日期"] , format='%Y%m%d') # 转换为日期时间格式
value_QDII.set_index("日期",inplace=True) # 将日期列设为value_QDII数据框的索引
value_QDII_diff = value_QDII.diff() # 计算基金每日净值的变动金额
print(value_QDII_diff.head()) #查看前五行数据
print(value_QDII_diff.tail()) #查看后五行数据
#任务四
import pandas as pd
# 从Excel文件读取数据存入value_QDII
value_QDII = pd.read_excel("https://huoran.oss-cn-shenzhen.aliyuncs.com/1726648479386.xlsx")
value_QDII_pctchangel = value_QDII.pct_change() #直接使用函数pct_change计算基金每日净值百分比变动
value_QDII_pctchangel.head() # 查看value_QDII_pctchangel前5行数据
value_QDII_pctchangel.tail() # 查看value_QDII_pctchangel后5行数据
value_QDII_diff = value_QDII.diff() # 计算差分值
value_QDII_pctchange2 = value_QDII_diff/value_QDII.shift(1) #运用任务三的结果计算基金每日净值百分比变动
print(value_QDII_pctchange2.head()) # 输出前几行数据
print(value_QDII_pctchange2.tail()) # 输出最后几行数据
任务复盘|平台任务解答代码
运行后核对:核对 value_QDII、value_QDII_diff、value_QDII_pctchangel、value_QDII_pctchange2 是否按预测参与运算,实际输出是否与预测一致;若不一致,先检查类型、单位、索引/字段和运算顺序。
本地执行更正(平台原码保持不变):任务四应先把“日期”转为日期类型并设为索引,再用 select_dtypes(include='number') 选择净值数值列,最后执行 pct_change();否则日期列可能报类型错误,或把日期编码误当连续数值计算收益率。
拓展练习:把输入表替换为本地中国上市公司数据的同结构子集;指出必须保持的字段、数据类型和质量检查。
基础统计量计算(课堂模拟)
数据声明:以下随机序列仅用于演示 Pandas 方法,不代表任何真实公司、基金或可交易结论。
describe() 返回统计量详解
| count |
非缺失值数量 |
\(n_{\text{valid}}\) |
| mean |
均值 |
\(\bar{x} = \frac{1}{n}\sum x_i\) |
| std |
标准差 |
\(\sqrt{\frac{1}{n-1}\sum(x_i-\bar{x})^2}\) |
| min |
最小值 |
\(\min(X)\) |
describe() 统计量详解(续)
| 25% |
第一四分位数 |
\(Q_1 = P_{25}\) |
| 50% |
中位数 |
\(Q_2 = P_{50}\) |
| 75% |
第三四分位数 |
\(Q_3 = P_{75}\) |
| max |
最大值 |
\(\max(X)\) |
异常值检测的数学原理
箱线图规则 (Boxplot Rule):
- 正常值:\([Q_1 - 1.5 \times IQR, \; Q_3 + 1.5 \times IQR]\)
- 温和异常值:距离箱体 \(1.5 \sim 3 \times IQR\)
- 极端异常值:距离箱体 \(> 3 \times IQR\)
Z-Score 方法:
\[ \large Z_i = \frac{x_i - \bar{x}}{\sigma} \]
通常认为 \(|Z| > 3\) 为异常值。
偏度:Pandas 的样本估计口径
令 \(m_k=\frac{1}{n}\sum_{i=1}^n(x_i-\bar{x})^k\)。Pandas Series.skew() 返回经偏差校正的 Fisher–Pearson 样本偏度:
\[ \large G_1=\frac{\sqrt{n(n-1)}}{n-2}\frac{m_3}{m_2^{3/2}},\qquad n>2 \]
- \(G_1 > 0\):右偏(正偏),右尾较长
- \(G_1 = 0\):样本偏度为零
- \(G_1 < 0\):左偏(负偏),左尾较长
峰度:Pandas 的样本估计口径
Pandas Series.kurtosis() 返回经偏差校正的 Fisher 超额峰度。先令 \(g_2=m_4/m_2^2-3\):
\[ \large G_2=\frac{n-1}{(n-2)(n-3)}\left[(n+1)g_2+6\right],\qquad n>3 \]
- \(G_2 > 0\):第四矩高于正态基准,可能来自尾部或中心质量差异
- \(G_2 = 0\):样本超额峰度为零
- \(G_2 < 0\):第四矩低于正态基准,不能单独推出尾部风险较低
这些样本统计量接近 0 只是分布形状线索,不能单独完成正态性检验。
偏度与峰度的金融意义
| 正偏度 |
右尾较长 |
右侧极端值候选;不等同于牛市或投资偏好 |
| 负偏度 |
左尾较长 |
左侧极端值候选;须结合分位数与期间判断风险 |
| 高峰度 |
第四矩较高 |
需用分布图、分位数和尾部指标继续诊断 |
一些金融市场样本会呈现负偏度 + 高峰度,但必须用目标期间的真实数据检验,不能由本课堂模拟直接推出。
累积统计量的金融应用
| cumsum |
累加可加的价格变化、现金流或成交量;简单收益率通常不可直接相加 |
| cumprod |
用 \((1+r_t)\) 连乘得到复合财富指数 |
| cummax |
回撤分析(Drawdown) |
| cummin |
历史最低价监控 |
最大回撤:衡量投资风险的关键指标
回撤 (Drawdown):从历史最高点到当前点的下降幅度
\[ \large \text{Drawdown}_t = \frac{P_t - \max_{i \leq t} P_i}{\max_{i \leq t} P_i} \]
最大回撤 (Maximum Drawdown, MDD):
\[ \large \text{MDD} = \min_t \text{Drawdown}_t \]
- MDD = -20% 意味着曾从高点下跌 20%
- 下跌 20% 需要上涨 25% 才能回本
净值与回撤可视化(课堂模拟)
展开净值与回撤绘图代码
import matplotlib.pyplot as plt
plt.rcParams['font.sans-serif'] = ['Source Han Serif SC']
plt.rcParams['axes.unicode_minus'] = False
fig, (ax1, ax2) = plt.subplots(2, 1, figsize=(12, 4.5), sharex=True) # 使用紧凑双面板画布以适配课堂投影
ax1.plot(nav['日期'], nav['净值'], label='净值', linewidth=2)
ax1.plot(nav['日期'], nav['历史最高'], label='历史最高', linewidth=2, linestyle='--')
ax1.set_ylabel('净值')
ax1.set_title('净值曲线与回撤分析')
ax1.legend()
ax1.grid(True, alpha=0.3)
ax2.fill_between(nav['日期'], nav['回撤'], 0, alpha=0.3, color='red')
ax2.plot(nav['日期'], nav['回撤'], color='red', linewidth=2)
ax2.set_ylabel('回撤率')
ax2.set_xlabel('日期')
ax2.grid(True, alpha=0.3)
plt.tight_layout()
plt.show()
滚动窗口统计:数学原理
滚动窗口 (Rolling Window):对时间序列 \(\{x_t\}_{t=1}^T\),窗口大小为 \(w\):
\[ \large \text{RollingMean}_t = \frac{1}{w}\sum_{i=t-w+1}^t x_i \]
核心思想:只用最近 \(w\) 个数据点计算统计量,捕捉短期动态变化。
布林带:基于滚动窗口的技术指标(课堂模拟)
布林带 (Bollinger Bands) 由三条线组成:
- 中轨:\(MA_{20}\)(20日移动平均)
- 上轨:\(MA_{20} + 2\sigma\)
- 下轨:\(MA_{20} - 2\sigma\)
边界触碰(未验证信号):价格触及上轨或下轨只说明相对滚动均值偏离较大;是否具有预测能力必须另做样本外回测,不能直接解释为超买、超卖或买卖建议。
rolling vs expanding 对比
| 窗口大小 |
固定(如5日、20日) |
不断增长(从起点到当前) |
| 计算范围 |
\([t-w+1, \; t]\) |
\([1, \; t]\) |
| 典型应用 |
移动平均、短期波动 |
累计收益、长期风险 |
分组统计 groupby:核心思想
分组聚合 (GroupBy Aggregation) 的三个步骤:
- Split(拆分):按键值将数据分成若干组
- Apply(应用):对每组数据应用聚合函数
- Combine(合并):将结果合并为一个新的数据结构
\[ \large \text{GroupBy}(K, f, X) = \{(k, f(\{x \mid key(x) = k\})) \mid k \in K\} \]
groupby 基础操作(课堂模拟)
数据声明:公司代码、名称与估值指标均为课堂构造,不对应任何真实公司或实时市场数据。
自定义聚合函数(课堂模拟)
口径说明:ROE 课堂标准化分数是“组均值减课堂基准”除以组内样本标准差;它没有收益时间序列或无风险利率,因此不是夏普比率。
协方差与相关系数
协方差 (Covariance):
\[ \large \text{Cov}(X, Y) = \frac{1}{n-1}\sum_{i=1}^n (x_i - \bar{x})(y_i - \bar{y}) \]
相关系数 (Correlation Coefficient):
\[ \large \rho_{X,Y} = \frac{\text{Cov}(X, Y)}{\sigma_X \sigma_Y} \]
- \(\rho \approx 1\):强正相关(同涨同跌)
- \(\rho \approx 0\):线性相关接近零,但不代表统计独立
- \(\rho \approx -1\):强负相关(此消彼长)
相关性在投资组合中的应用
现代投资组合理论 (MPT):
\[ \large \sigma_p^2 = \sum_{i=1}^n \sum_{j=1}^n w_i w_j \sigma_i \sigma_j \rho_{ij} \]
| \(\rho \approx 1\) |
同涨同跌 |
分散化效果差 |
| \(\rho \approx 0\) |
线性联动弱,不等于独立 |
需结合非线性依赖与情景压力测试 |
| \(\rho \approx -1\) |
此消彼长 |
天然对冲工具 |
在给定权重与边际方差下,资产并非完全正相关通常提供分散化空间;实际组合风险仍取决于完整协方差矩阵、权重和样本稳定性。
本章小结
| 描述性统计 |
mean(), std(), describe() |
收益率分析 |
| 分位数与异常值 |
quantile(), IQR 规则 |
风险监控 |
| 偏度与峰度 |
skew(), kurtosis() |
分布特征判断 |
| 累积统计 |
diff().cumsum(), (1+r).cumprod(), cummax() |
变化重构、复合财富与回撤分析 |
| 滚动窗口 |
rolling(), expanding() |
移动平均、布林带 |
| 分组统计 |
groupby(), agg() |
行业对比分析 |
| 相关性分析 |
corr(), cov() |
投资组合优化 |
随堂练习
- 问题 1|需要准备哪些数据?:中性净值、收益率与行业分组教学示例。
- 问题 2|需要完成哪些操作?:运行
lst-drawdown、lst-rolling-stats 与 lst-correlation,计算回撤、滚动统计和关系矩阵。
- 问题 3|应得到哪些结果?:最大回撤表、滚动窗口尾值、最大带符号相关唯一对与绝对值最强相关唯一对。
- 问题 4|怎样确认结果可靠?:手算峰谷回撤,排除对角/重复对,并分别用原值
idxmax() 与绝对值 idxmax() 核对;口径混写时先检查原因。
- 问题 5|换一个情境,怎样继续应用?:把同一组统计方法应用到本地真实净值,并说明数据来源。
- 作答提示:请依次写清所用数据、分析过程、所得结果、核对方法和拓展思考。课程所需数据见前言中的下载入口;教学平台固定题按页面说明完成。
教师参考解答|答案与说明 1
- 所用数据与字段:按日期升序的小数收益率;无缺失、无重复日期。
教师参考解答|代码 1
展开代码(代码区可独立滚动)
import numpy as np # 导入数值计算库以检查有限值
import pandas as pd # 导入表格工具以构造、对齐和核对数据
returns=pd.DataFrame({'资产甲':[.03,-.02,.01,.04,-.03,.02,.01,-.01],'资产乙':[.02,-.01,.00,.03,-.02,.01,.02,-.01],'资产丙':[-.01,.02,.01,-.02,.03,.00,-.01,.02]},index=pd.date_range('2025-01-03',periods=8,freq='W-FRI')) # 建立同频中性收益示例数据
assert returns.index.is_monotonic_increasing and returns.index.is_unique and np.isfinite(returns).all().all() # 执行日期与有限值输入要求
wealth=(1+returns).cumprod() # 计算各资产复合净值
drawdown=wealth/wealth.cummax()-1 # 计算各资产回撤路径
maximum_drawdown=drawdown.min() # 提取各资产最大回撤
rolling_mean=returns.rolling(4,min_periods=4).mean().iloc[-1] # 提取四期滚动均值尾值
correlation=returns.corr() # 计算完整关系矩阵
upper=np.triu(np.ones(correlation.shape,dtype=bool),k=1) # 构造排除对角与镜像的上三角掩码
unique_pairs=correlation.where(upper).stack().rename('correlation') # 生成保留符号的唯一资产对序列
signed_pair=unique_pairs.loc[[unique_pairs.idxmax()]] # 用原值idxmax选择最大带符号相关唯一对
absolute_pair=unique_pairs.loc[[unique_pairs.abs().idxmax()]] # 用绝对值idxmax选择方向不限的最强唯一对
asset='资产甲' # 指定另行核对资产
trough=drawdown[asset].idxmin() # 定位该资产最大回撤谷值日
peak=wealth.loc[:trough,asset].idxmax() # 定位谷值日前历史峰值日
assert np.isclose(maximum_drawdown[asset],wealth.loc[trough,asset]/wealth.loc[peak,asset]-1) # 手算核对峰谷回撤
assert len(unique_pairs)==3 and not any(left==right for left,right in unique_pairs.index) # 检查三资产仅有三个唯一资产对
assert signed_pair.index[0]==('资产甲','资产乙') and np.isclose(signed_pair.iloc[0],.938979,atol=1e-6) # 检查示例数据最大带符号相关为+0.938979
assert absolute_pair.index[0]==('资产乙','资产丙') and np.isclose(absolute_pair.iloc[0],-1.0) # 检查示例数据绝对值最强相关为-1
print({'最大回撤':maximum_drawdown.round(4).to_dict(),'滚动均值尾值':rolling_mean.round(4).to_dict(),'最大带符号相关唯一对':signed_pair.round(6).to_dict(),'绝对值最强相关唯一对':absolute_pair.round(6).to_dict()}) # 输出四类统计结果
教师参考解答|答案与说明 2
- 解释答案:累计净值、运行峰值和回撤必须按时间顺序计算;滚动尾值必须标明窗口;相关输出必须排除对角与镜像重复且不解释为因果。
- 拓展应用答案|本地真实中国基金净值:
教师参考解答|代码 2
展开代码(代码区可独立滚动)
from pathlib import Path # 导入路径工具以执行数据来源要求
import numpy as np # 导入数值计算库以执行有限值检查
import pandas as pd # 导入表格工具读取本地基金行情
source=Path('/home/ubuntu/r2_data_mount/data/fund/funds.h5') # 锁定本地真实基金数据来源
symbols=['510300.XSHG','510500.XSHG','159915.XSHE'] # 定义三只中国ETF新案例对象
required={'symbol','datetime','close'} # 定义最小字段要求
assert source.exists() # 来源缺失时应停止并检查数据
raw=pd.read_hdf(source,key='funds') # 读取本地真实基金行情快照
assert required.issubset(raw.columns) # 字段结构缺失时应停止并检查数据
raw=raw.loc[raw['symbol'].isin(symbols),['symbol','datetime','close']].copy() # 仅保留新案例资产与必要字段
raw['date']=pd.to_datetime(raw['datetime'].astype(str),format='%Y%m%d%H%M%S',errors='coerce') # 把数据日期规范为时间戳
raw['close']=pd.to_numeric(raw['close'],errors='coerce') # 把收盘净值规范为数值
assert set(raw['symbol'])==set(symbols) and raw['date'].notna().all() and np.isfinite(raw['close']).all() and (raw['close']>0).all() # 资产、日期、有限值或正值失败时请检查数据
nav=raw.pivot_table(index='date',columns='symbol',values='close',aggfunc='last').sort_index().loc['2023-01-01':'2025-12-31'].dropna() # 对齐真实净值期间
assert nav.index.is_unique and nav.index.is_monotonic_increasing and len(nav)>=60 # 日期重复、乱序或窗口不足时请检查数据
fund_returns=nav.pct_change(fill_method=None).dropna() # 计算同频基金收益
fund_drawdown=(nav/nav.cummax()-1).min() # 计算各基金最大回撤
fund_rolling=fund_returns.rolling(20,min_periods=20).mean().iloc[-1] # 计算20交易日滚动收益尾值
fund_corr=fund_returns.corr() # 计算真实基金收益关系矩阵
fund_pairs=fund_corr.where(np.triu(np.ones(fund_corr.shape,dtype=bool),1)).stack() # 排除对角和镜像重复资产对
fund_signed=fund_pairs.loc[[fund_pairs.idxmax()]] # 选择真实新案例中的最大带符号相关唯一对
fund_absolute=fund_pairs.loc[[fund_pairs.abs().idxmax()]] # 选择真实新案例中的绝对值最强相关唯一对
assert len(fund_pairs)==3 and np.isfinite(fund_drawdown).all() and np.isfinite(fund_rolling).all() # 检查统计结果完整且有限
assert fund_signed.iloc[0]==fund_pairs.max() and abs(fund_absolute.iloc[0])==fund_pairs.abs().max() # 另行核对两种排序口径
print({'来源':str(source),'期间':[nav.index.min().date(),nav.index.max().date()],'最大回撤':fund_drawdown.round(4).to_dict(),'滚动尾值':fund_rolling.round(6).to_dict(),'最大带符号相关唯一对':fund_signed.round(4).to_dict(),'绝对值最强相关唯一对':fund_absolute.round(4).to_dict()}) # 输出来源绑定新案例结果
教师参考解答|答案与说明 3
- 参考结果:最大回撤、滚动窗口尾值、最大带符号相关唯一对与绝对值最强相关唯一对;拓展应用输出另含来源与实际期间。另行核对:示例数据中分别选出+0.938979与-1,并检查三资产恰有三个唯一对。
- 常见错误:把“最大带符号相关”按绝对值排序;输出绝对强度却丢掉负号;保留自相关或镜像对。